/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_st7789.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "esp_lvgl_port.h"

#include "esp_lcd_panel_vendor.h"

#include "board_v1_1_map.h"

#define log_e(...) ESP_LOGE(TAG, __VA_ARGS__)
#define log_i(...) ESP_LOGI(TAG, __VA_ARGS__)
#define log_w(...) ESP_LOGW(TAG, __VA_ARGS__)

static const char *TAG = "lcd";

#define SPI_HOST_ID SPI3_HOST
#define SPI_CLOCK_HZ (80 * 1000 * 1000)

static esp_lcd_panel_handle_t panel_handle = NULL;
static lv_disp_drv_t disp_drv = {0};
static uint8_t *lv_buf_tans = NULL;
static lv_disp_draw_buf_t disp_buf = {0};
static lv_indev_drv_t indev_drv = {0};
static esp_lcd_panel_io_handle_t io_handle = NULL;
static SemaphoreHandle_t lvgl_mux = NULL;
static bool lcd_inited = false;

__attribute__((weak)) void ui_init(void)
{
    log_e("This is default ui init function, please implement it in your project");

    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "This is default ui init");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

typedef struct
{
    int cmd;               /*<! The specific LCD command */
    const void *data;      /*<! Buffer that holds the command specific data */
    size_t data_bytes;     /*<! Size of `data` in memory, in bytes */
    unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} lcd_init_cmd_t;

static const lcd_init_cmd_t vendor_specific_init[] = {
    {0x11, (uint8_t[]){0}, 0, 120},                          /* 退出休眠 */
    {0x3A, (uint8_t[]){0x05}, 1, 0},                         /* 06 */
    {0xC5, (uint8_t[]){0x1A}, 1, 0},                         /* VCOMS Offset Set 0x20  */
    {0x36, (uint8_t[]){0x00}, 1, 0},                         /* RGB */
    {0xB2, (uint8_t[]){0x05, 0x05, 0x00, 0x33, 0x33}, 5, 0}, /* Porch Setting */
    {0xB7, (uint8_t[]){0x05}, 1, 0},                         /* Gate Control 12.2v   -10.43v */
    {0xBB, (uint8_t[]){0x3F}, 1, 0},                         /* VCOM */
    {0xC0, (uint8_t[]){0x2C}, 1, 0},                         /* Power control */
    {0xC2, (uint8_t[]){0x01}, 1, 0},                         /* VDV and VRH Command Enable */
    {0xC3, (uint8_t[]){0x0F}, 1, 0},                         /* VRH Set 4.3+( vcom+vcom offset+vdv) */
    {0xC4, (uint8_t[]){0x20}, 1, 0},                         /* VDV Set 0V */
    // {0xC6, (uint8_t[]){0x00}, 1,0}, /* 显示119HZ */
    // {0xC6, (uint8_t[]){0x03}, 1,0}, /* 显示99HZ */
    // {0xC6, (uint8_t[]){0x05}, 1,0}, /* 显示90HZ */
    {0xC6, (uint8_t[]){0x09}, 1, 0},             /* 显示75HZ */
                                                 // {0xC6, (uint8_t[]){0x0F}, 1,0}, /* 显示60HZ */
                                                 // {0xC6, (uint8_t[]){0x15}, 1,0}, /* 显示50HZ */
                                                 // {0xC6, (uint8_t[]){0x1E}, 1,0}, /* 显示40HZ */
                                                 // {0xC6, (uint8_t[]){0x1F}, 1,0}, /* 显示39HZ */
    {0xD0, (uint8_t[]){0xA4, 0xA1}, 2, 0},       /* Power Control 1 */
    {0xE8, (uint8_t[]){0x03}, 1, 0},             /* Power Control 1 */
    {0xE9, (uint8_t[]){0x09, 0x09, 0x08}, 3, 0}, /* Porch Setting */
    {0xE0, (uint8_t[]){0xD0, 0x05, 0x09, 0x09, 0x08, 0x14, 0x28, 0x33, 0x3F, 0x07, 0x13, 0x14, 0x28, 0x30}, 14, 0},
    {0xE1, (uint8_t[]){0xD0, 0x05, 0x09, 0x09, 0x08, 0x03, 0x24, 0x32, 0x32, 0x3B, 0x14, 0x13, 0x28, 0x2F}, 14, 0},
    {0x21, (uint8_t[]){0}, 0}, /* 反显 */
    {0x35, (uint8_t[]){0}, 0}, //      TE
    {0x29, (uint8_t[]){0}, 0}, //      开启显示
};

static const lcd_init_cmd_t vendor_specific_init_off[] = {
    {0x28, (uint8_t[]){0}, 120}, /* 关闭显示 */
    {0x10, (uint8_t[]){0}, 0},   /* 进入休眠 */
};

static void button_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    // static bool last_state = false;
    static uint32_t last_tick = 0;
    static uint32_t last_key = 0;
    static uint8_t last_id = 0;
    static bool check_db = false;
    static bool dbclick = false;
    static bool longEnter = false;

    bool curr_state = !gpio_get_level(BTN_PIN);
    data->btn_id = last_id;
    data->key = last_key;
    data->state = LV_INDEV_STATE_RELEASED;
    if (curr_state)
    {
        if (!last_tick)
        {
            last_tick = lv_tick_get();
            /* 按下 */
        }
        else if (check_db == true)
        {
            /* 双击 */
            if (lv_tick_elaps(last_tick) < 500)
                dbclick = true;
            check_db = false;
        }
        else if (lv_tick_elaps(last_tick) > 500) /* 长按 */
        {
            last_key = LV_KEY_HOME;
            data->key = LV_KEY_HOME;
            data->btn_id = 2;
            data->state = LV_INDEV_STATE_PRESSED;
            longEnter = true;
            // printf("长按\r\n");
        }
    }
    else if (last_tick)
    {
        /* 松手 */
        if (longEnter)
        {
            longEnter = false;
            check_db = false;
            dbclick = false;
            last_tick = 0;
        }
        else if (dbclick)
        {
            dbclick = false;
            last_key = LV_KEY_ENTER;
            data->key = LV_KEY_ENTER;
            data->btn_id = 1;
            data->state = LV_INDEV_STATE_PRESSED;
            // printf("双击\r\n");
            last_tick = 0;
        }
        else if (lv_tick_elaps(last_tick) > 300)
        {
            /* 单击 */
            last_key = LV_KEY_DOWN;
            data->key = LV_KEY_DOWN;
            data->btn_id = 0;
            data->state = LV_INDEV_STATE_PRESSED;
            last_tick = 0;
            check_db = false;
            // printf("单击\r\n");
        }
        else
            check_db = true;
    }
}

void lcd_pannel_on(uint8_t on)
{
    if (!io_handle)
        return;

    static const uint8_t init_cmds_size = sizeof(vendor_specific_init) / sizeof(lcd_init_cmd_t);
    static const uint8_t init_cmds_size_off = sizeof(vendor_specific_init_off) / sizeof(lcd_init_cmd_t);

    if (!lcd_inited || lvgl_port_lock(0))
    {
        if (on)
        {
            for (int i = 0; i < init_cmds_size; i++)
            {
                esp_lcd_panel_io_tx_param(io_handle, vendor_specific_init[i].cmd, vendor_specific_init[i].data, vendor_specific_init[i].data_bytes);
                if (vendor_specific_init[i].delay_ms)
                    vTaskDelay(pdMS_TO_TICKS(vendor_specific_init[i].delay_ms));
            }
        }
        else
        {
            for (int i = 0; i < init_cmds_size_off; i++)
            {
                esp_lcd_panel_io_tx_param(io_handle, vendor_specific_init_off[i].cmd, vendor_specific_init_off[i].data, vendor_specific_init_off[i].data_bytes);
                if (vendor_specific_init_off[i].delay_ms)
                    vTaskDelay(pdMS_TO_TICKS(vendor_specific_init_off[i].delay_ms));
            }
        }
        if (lcd_inited)
            lvgl_port_unlock();
    }
}

void *lcd_init(void)
{

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = CONFIG_LCD_SPI_RST_PIN,
        .color_space = ESP_LCD_COLOR_SPACE_RGB,
        .bits_per_pixel = 16,
    };

    spi_bus_config_t buscfg = {
        .sclk_io_num = CONFIG_LCD_SPI_SCL_PIN,
        .mosi_io_num = CONFIG_LCD_SPI_SDA_PIN,
        .miso_io_num = CONFIG_LCD_SPI_PIN_SDO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = CONFIG_LCD_H_RES * CONFIG_LCD_V_RES * sizeof(uint16_t)};

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = CONFIG_LCD_SPI_DC_PIN,
        .cs_gpio_num = CONFIG_LCD_SPI_CS_PIN,
        .spi_mode = 0,
        .pclk_hz = SPI_CLOCK_HZ,
        .trans_queue_depth = 100,
        .user_ctx = NULL,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
    };

    log_i("spi bus initialize scl(%d) sda(%d) sdo(%d) dc(%d) cs(%d) te(%d) rst(%d) freq(%d)",
          CONFIG_LCD_SPI_SCL_PIN,
          CONFIG_LCD_SPI_SDA_PIN,
          CONFIG_LCD_SPI_PIN_SDO,
          CONFIG_LCD_SPI_DC_PIN,
          CONFIG_LCD_SPI_CS_PIN,
          CONFIG_LCD_SPI_TE_PIN,
          CONFIG_LCD_SPI_RST_PIN,
          SPI_CLOCK_HZ / 1000 / 1000);
    spi_bus_free(SPI_HOST_ID);

    esp_err_t ret = spi_bus_initialize(SPI_HOST_ID, &buscfg, SPI_DMA_CH_AUTO);
    if (ret)
        log_e("Failed to initialize SPI BUS(%s)", esp_err_to_name(ret));

    ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_HOST_ID, &io_config, &io_handle);
    if (ret)
        log_e("Failed to new SPI panel(%s)", esp_err_to_name(ret));

    log_i("lcd driver ic initialize , Freq:%dKhz", io_config.pclk_hz / 1000);

    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    esp_lcd_panel_set_gap(panel_handle, 0, 35);
    lcd_pannel_on(1);

    ESP_LOGI(TAG, "Initialize LVGL");
    lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
    lvgl_cfg.task_affinity = 1;
    lvgl_cfg.task_priority = 10;
    lvgl_port_init(&lvgl_cfg);

    const lvgl_port_display_cfg_t disp_cfg = {
        .io_handle = io_handle,
        .panel_handle = panel_handle,
        .buffer_size = CONFIG_LCD_H_RES * CONFIG_LCD_V_RES,
#ifdef CONFIG_SPIRAM
        .double_buffer = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > CONFIG_LCD_H_RES * CONFIG_LCD_V_RES * 4,
#endif
        .trans_size = CONFIG_LCD_H_RES * CONFIG_LCD_V_RES / 2,
        .hres = CONFIG_LCD_H_RES,
        .vres = CONFIG_LCD_V_RES,
#if LVGL_VERSION_MAJOR >= 9
        .color_format = LV_COLOR_FORMAT_RGB565,
#endif
        .rotation = {
            .swap_xy = true,
            .mirror_x = true,
            .mirror_y = false,
        },
        .flags = {
#if LVGL_VERSION_MAJOR >= 9
            .swap_bytes = true,
#endif
            .sw_rotate = false,
    // .full_refresh = true,
#ifdef CONFIG_SPIRAM
            .buff_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > CONFIG_LCD_H_RES * CONFIG_LCD_V_RES * 4,
#endif
        }};
    lv_disp_t *disp = lvgl_port_add_disp(&disp_cfg);

    // 创建LVGL输入设备
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;
    indev_drv.read_cb = button_read;
    lv_indev_t *cur_drv = lv_indev_drv_register(&indev_drv);

    lv_group_t *g = lv_group_get_default();
    if (g == NULL)
    {
        g = lv_group_create();
        lv_group_set_default(g);
    }
    lv_indev_set_group(cur_drv, g);

    lcd_inited = true;

    if (lvgl_port_lock(0))
    {
        ui_init();
        lvgl_port_unlock();
    }
    return disp;
}
